Skip to content

feat(cursor): wire /models /agents /cancel to cursor-agent configOptions#492

Closed
brettchien wants to merge 3 commits intoopenabdev:mainfrom
brettchien:feat/cursor-config-options-discord
Closed

feat(cursor): wire /models /agents /cancel to cursor-agent configOptions#492
brettchien wants to merge 3 commits intoopenabdev:mainfrom
brettchien:feat/cursor-config-options-discord

Conversation

@brettchien
Copy link
Copy Markdown
Contributor

@brettchien brettchien commented Apr 20, 2026

Closes #489.

Discussion: https://discord.com/channels/1491295327620169908/1491365150664560881/1495774750226645133

Upstream cursor-agent ACP compliance gaps tracked separately in #493.

Summary

  • Discord slash commands /models, /agents, /cancel now work end-to-end against cursor-agent acp.
  • Each handler: defer_ephemeralpool.get_or_create(thread_key) → build select menu from the session's cached configOptionsedit_response. No more "start a conversation first" dead-end, and the 3s Discord interaction deadline is honoured even on cold-start sessions.
  • StringSelectMenu capped at Discord's 25-option limit with a plain take(25) — no vendor-specific pinning. cursor-agent returns 26 models on a Pro account; the last one is dropped.
  • agent / mode category treated as aliases so the same /agents handler works against both kiro-cli (category=agent) and cursor-agent (category=mode). Per ACP spec the reserved value is "mode".
  • Trust-the-protocol: OpenAB does not probe or auto-rewrite after set_config_option. If cursor-agent accepts an unroutable model with setOk:true and then soft-rejects on the next prompt, that soft-reject is passed through to the channel. Upstream bug tracked in tracking: cursor-agent ACP compliance issues — upstream bugs affecting /models #493.

Changes

  • src/discord.rs — new handle_config_command (shared by /models and /agents), handle_config_select, handle_cancel_command; build_config_select with ACP-agnostic 25-option cap; category alias table.
  • config.toml.example — documents the --model auto startup gotcha in the cursor-agent section.
  • docs/cursor.md — Model Selection section with startup --model auto table and passthrough semantics; new Known Limitations subsection listing three upstream cursor-agent ACP compliance gaps (all linked to tracking: cursor-agent ACP compliance issues — upstream bugs affecting /models #493).

No changes in src/acp/connection.rs, src/acp/pool.rs, or src/config.rs — ACP layer untouched.

Test plan

  • cargo check clean
  • cargo test --release — 74/74 pass
  • Manual E2E against openab-cursor pod running cursor-agent 2026.04.15-dccdccd:
    • /models lists 25 models (truncated from 26) with the current value highlighted
    • Switching to a routable model (composer-2) → ✅ Switched to composer-2, next prompt routes there
    • /agents lists Agent / Plan / Ask (regression from v0.7.8 where it returned "No agent options available" against cursor-agent)
    • /cancel against an in-flight prompt → session/cancelstopReason: cancelled (~15ms roundtrip, verified in pod logs)
    • Soft-reject passthrough observed — cursor-agent streamed "\n\nCheck your settings to continue" for an unroutable model and OpenAB forwarded it unchanged (new variant beyond the AI Model Not Found / Model name is not valid family, noted for tracking: cursor-agent ACP compliance issues — upstream bugs affecting /models #493)

Revision history

  • v1: included a proactive per-model probe that sent a silent ping, detected cursor-agent's plain-text soft-reject, and auto-fell-back to default[] on hit.
  • v2 (per maintainer review): probe + fallback removed. That policy belongs upstream, not in OpenAB; the trust-the-protocol approach keeps set_config_option semantics intact and surfaces the upstream bug directly in-channel.
  • v3: truncation pinning (current + "default[]") also removed — kept as a plain in-order take(25) so build_config_select has no vendor literals.

🤖 Generated with Claude Code

@brettchien brettchien requested a review from thepagent as a code owner April 20, 2026 13:08
@github-actions github-actions Bot added closing-soon PR missing Discord Discussion URL — will auto-close in 3 days and removed closing-soon PR missing Discord Discussion URL — will auto-close in 3 days labels Apr 20, 2026
@shaun-agent
Copy link
Copy Markdown
Contributor

OpenAB PR Screening

This is a manually uploaded screening report from the OpenAB project-screening flow for context collection and reviewer handoff.

Screening report

Intent

pr #492 is trying to finish the cursor-specific user experience around discord slash commands without turning openab into a cursor workaround layer.

the concrete problem is twofold. first, cursor-backed channels currently miss the intended /models, /agents, and /cancel control path because the handlers assume a session already exists and can run out of time against discord's 3 second interaction deadline. second, cursor-agent exposes broken ACP behavior around model selection, so the pr also tightens the docs to explain what openab will and will not do when the backend lies.

assumption: deploy-time cursor support already exists in-tree through the existing cursor image, helm values, and docs, so this pr is intentionally about discord interaction flow and policy boundary, not first-time packaging.

Feat

this is a feature and interaction-hardening pass centered in src/discord.rs, with supporting docs and config example updates.

in plain terms, it makes cursor sessions behave like a real first-class ACP backend for discord controls:

  • slash commands defer immediately so slow or cold session startup does not blow the interaction deadline
  • the handler creates or restores the pooled ACP session before reading cached configOptions
  • /agents works for both category=agent and category=mode
  • /models respects discord's 25-option select limit with a plain in-order truncation
  • /cancel becomes usable against an active cursor session through the same deferred-response pattern
  • docs now state that --model auto is load-bearing and that openab passes cursor soft rejects through instead of probing or auto-falling back

notably, the ACP transport, pool internals, and config parsing layer stay untouched. that is a good boundary.

Who It Serves

the primary beneficiary is discord users running openab against cursor-agent acp, especially users on a fresh channel session who previously hit the "start a conversation first" dead-end or a silent timeout.

secondarily it serves maintainers and deployers. the docs now make the startup model invariant and upstream cursor-agent defects explicit, which reduces false debugging loops where openab gets blamed for cursor's bad protocol behavior.

Rewritten Prompt

implement cursor-compatible discord config controls without adding cursor-specific heuristics to the ACP layer.

acceptance criteria:

  • /models, /agents, and /cancel must call defer_ephemeral before any potentially slow work
  • config commands must call pool.get_or_create(thread_key) before reading configOptions, so they work on cold-start sessions and after pool eviction
  • config select handling must re-run get_or_create before set_config_option
  • build_config_select must treat agent and mode as aliases and cap string select menus to 25 options in order
  • successful config changes must be acknowledged via edit_response
  • failures to create a session or set an option must surface a direct error in the interaction response
  • docs/cursor.md and config.toml.example must explain that --model auto is required for true auto routing
  • openab must not probe, string-match, or auto-fallback after set_config_option; upstream cursor-agent ACP defects remain documented in #493

validation:

  • manual e2e against a live cursor-agent deployment for /models, /agents, and /cancel
  • verify cold-start command use works before the first normal prompt
  • verify a pool-evicted session can still switch config after the menu interaction

if issue #489 still has deployer ergonomics left over, split them into a follow-up instead of growing this pr sideways.

Merge Pitch

this is worth merging because it closes a real product gap for cursor-backed deployments with relatively tight scope. users get the same slash-command control surface other ACP backends already expose, and the implementation does it in the right layer: discord handler and docs, not protocol-shaping hacks.

risk is moderate but localized. the code changes sit in interaction timing and pooled session bootstrapping, which can regress command UX if wrong, but they do not alter the ACP core. the likely reviewer concern is not the handler logic itself. it is whether this pr is claiming to close more than it actually does, because #489 originally mixed UI wiring, deploy config, and a probe-based workaround that the project has since rejected.

Best-Practice Comparison

OpenClaw: the relevant principle is boundary discipline. route work explicitly, surface failures honestly, and do not smuggle backend-specific recovery rules into the wrong layer. this pr matches that better than the earlier probe-and-fallback direction. durable job persistence and retry/backoff are not directly relevant here because this is an interactive discord control path, not a scheduled execution pipeline.

Hermes Agent: the relevant principle is making state transitions explicit before acting. the defer-first plus get_or_create flow is a small but meaningful version of that: establish session state before assuming cached config exists. file locking, atomic writes, and fresh-session scheduling are not relevant to this path.

net: the useful lesson from both reference systems is not their runtime architecture. it is their refusal to paper over undefined state with wishful logic. #492 is stronger after removing the proactive probe because it keeps openab's contract clean.

Implementation Options

  1. merge this pr as the narrow discord-and-docs slice, and split any remaining cursor deployer ergonomics into a separate follow-up.

  2. broaden this pr slightly to also add or normalize default cursor deployment artifacts if maintainers still think #489 requires a repo-native config entry point beyond the existing helm/docs path.

  3. reintroduce an openab-side probe, entitlement filter, or auto-fallback layer to hide cursor-agent's broken model semantics from users.

Comparison Table

Option Speed to ship Complexity Reliability Maintainability User impact Fit for OpenAB right now
1. merge as a narrow slice high low high high high very high
2. widen scope to include deploy artifacts medium medium medium-high medium high medium
3. add openab-side workaround logic low high medium low medium-high in the short term low

Recommendation

take option 1.

the pr is strongest when treated as a focused interaction-layer improvement plus documentation cleanup. it fixes a real user-facing gap, keeps the ACP boundary honest, and avoids merging policy-confused workaround code just because cursor-agent is sloppy. if maintainers still want a dedicated config/cursor.toml, compose profile, or similar deploy-time artifact, do that as a follow-up and do not use it to block the handler fix.

the one thing to watch in review is scope accounting: if #489 is meant to cover more than this pr actually ships, close only the part that is now done and leave the rest as explicit follow-up work.

brettchien and others added 3 commits April 21, 2026 09:47
Closes openabdev#489.

Key changes:
- discord slash handlers defer_ephemeral first, then get_or_create the
  pooled ACP session, then edit_response with the select menu built from
  the session's cached configOptions.
- /models runs a silent "ping" probe after set_config_option to catch
  cursor-agent's soft-reject (`AI Model Not Found` / `Model name is
  not valid`) and falls back to `default[]` (Auto). Probe timeout is
  configurable via `[agent].probe_timeout_secs` (default 15s); on
  timeout the probe is cancelled and the same fallback runs.
- Cap StringSelectMenu at Discord's 25-option limit, preserving the
  current value and `default[]` when truncating (cursor-agent returns
  26 models on a Pro account).
- Treat `agent` and `mode` categories as aliases so the same `/agents`
  command works against both kiro-cli (category=agent) and cursor-agent
  (category=mode).

Adds `ProbeResult` + `probe_prompt` on AcpConnection and `probe_session`
+ `probe_timeout_secs` on SessionPool. Docs updated in
`docs/cursor.md` with the /models behaviour + probe semantics.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Strip the proactive probe + auto-fallback-to-Auto workaround for
cursor-agent's soft-reject behaviour. Per project policy we trust the
ACP protocol: `setOk:true` means the switch succeeded. The upstream
cursor-agent bug where `set_config_option` accepts unroutable models is
tracked in openabdev#493 and reported via the Cursor forum.

Retained:
- /models, /agents, /cancel wired to ACP `configOptions`
- `defer_ephemeral` + `edit_response` pattern to avoid Discord 3s deadline
- Pre-flight `get_or_create` so the command works before first @mention
- 25-option cap on Discord select menus (cursor-agent returns 26 models
  on Pro accounts); current/default pinned when truncating
- `agent`/`mode` category alias for kiro-cli compatibility

Removed:
- `AcpConnection::probe_prompt` and `ProbeResult` (connection.rs)
- `SessionPool::probe_session` and `probe_timeout_secs` (pool.rs)
- `AgentConfig.probe_timeout_secs` field (config.rs)
- `Handler::fallback_to_auto` and all /models probe logic (discord.rs)
- `probe_timeout_secs` example in config.toml.example
- Proactive-probe section in docs/cursor.md, replaced with
  "Soft rejects from unroutable models" passthrough explanation

docs/cursor.md now includes a Known Limitations subsection listing the
three upstream ACP compliance gaps (setOk for unroutable models,
unfiltered model list, process-local loadSession), all linked to openabdev#493.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop the current-value + `default[]` pinning logic inside
`build_config_select`. The pin was motivated by preserving Auto when
truncating from 26 to 25 cursor-agent models, but `"default[]"` is a
cursor-specific literal and the extra complexity buys little — Auto is
already first in cursor-agent's model list, so a straight in-order
`take(25)` keeps it. When `current_value` sits at position >25 the
dropdown won't show it as the default, but the live session retains
the selection on cursor's side; user can switch again by rerunning
`/models`.

This keeps `build_config_select` ACP-agnostic: no vendor literals, no
assumptions about which option is the "fallback".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@shaun-agent shaun-agent force-pushed the feat/cursor-config-options-discord branch from 87a0e09 to 85a22b4 Compare April 21, 2026 09:48
Copy link
Copy Markdown
Contributor

@masami-agent masami-agent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: #492

Summary

  • Problem: Discord slash commands /models, /agents, /cancel did not work against cursor-agent because the handlers assumed a session already existed and used create_response which could exceed Discord's 3-second interaction deadline on cold starts.
  • Approach: Switch all three handlers to defer_ephemeral + edit_response, add a pre-flight get_or_create to ensure a session (and its cached configOptions) exists, alias agent/mode categories for kiro-cli/cursor-agent compatibility, and cap the select menu at Discord's 25-option limit with a plain take(25).
  • Risk level: Low

Core Assessment

  1. Problem clearly stated: ✅ — linked to #489, thorough PR description with revision history
  2. Approach appropriate: ✅ — minimal changes scoped to discord.rs, no ACP layer modifications
  3. Alternatives considered: ✅ — v1 (probe+fallback) → v2 (trust-the-protocol) → v3 (simplified truncation), well-documented iteration
  4. Best approach for now: ✅ — trust-the-protocol is the right call; upstream bugs belong upstream (#493)

Findings

build_config_select — category aliasing and truncation

The alias table is clean and handles the kiro-cli (agent) vs cursor-agent (mode) divergence correctly. The take(25) truncation is ACP-agnostic — no vendor literals, no assumptions about which option is the fallback. Good simplification from v3.

handle_config_command — defer + get_or_create + edit

The defer_ephemeralget_or_createget_config_optionsedit_response sequence is correct. get_or_create handles both missing-session and evicted-session cases, and session/new populates config_options (confirmed in connection.rs:358), so the subsequent get_config_options call will have data. The error message on get_or_create failure is clear and actionable.

handle_cancel_command — defer + cancel

Using defer_ephemeral here is consistent with the other handlers. cancel_session uses pre-stored cancel_handles and doesn't require a live session lock, so the "no session for thread" error on missing session is fine UX — it tells the user there's nothing to cancel.

handle_config_select — defensive get_or_create

The defensive get_or_create before set_config_option is a good catch — the pool could evict the session between the initial /models interaction and the user's selection. This closes a real race window.

Docs (cursor.md)

The --model auto startup table is a valuable addition — this is a non-obvious gotcha. The "Soft rejects from unroutable models" section clearly explains the trust-the-protocol semantics and links to #493. Known Limitations is well-structured.

Review Summary

🔴 Blockers

(none)

💬 Questions

(none)

🔧 Suggested Changes

(none)

ℹ️ Info

  • In handle_config_select, the display_name extraction changed from .name.as_str() (borrowing) to .name.clone() (owning). Functionally identical since the value is immediately consumed by format!(), but worth noting as a minor style divergence from the original. Not worth changing.
  • The commit history (v1 → v2 → v3) is well-structured and tells a clear story. The squash merge will collapse it, but the PR description preserves the rationale nicely.

⚪ Nits

(none)

Verdict

APPROVE — clean, well-scoped PR with no blockers. The defer+edit pattern, pre-flight session creation, and trust-the-protocol approach are all correct. Docs are thorough.

Copy link
Copy Markdown
Collaborator

@obrutjack obrutjack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Clean defer-first pattern, correct category aliasing, ACP layer untouched. Two minor suggestions (success-path error logging, alias comment) — non-blocking. Ship it.

Copy link
Copy Markdown
Contributor

@shaun-agent shaun-agent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no blockers from my pass. the final revision keeps the fix in the discord handler layer: defer/edit interactions, get_or_create before config reads, agent/mode aliasing, and an ACP-agnostic 25-option cap.\n\nCI is green. local cargo test was not runnable in this pod because there is no C linker (cc), so build validation is from CI.

Copy link
Copy Markdown
Collaborator

@chaodu-agent chaodu-agent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Needs Rebase + Rework — Good improvements but stale against main; regresses pagination.

Baseline Check (Step 0)
Field Value
State OPEN
Mergeable CONFLICTING
Created 2026-04-20 (11 days ago)
Changes +152 / -58 across 3 files
Closes #489

Main branch status: Main now has pagination support (PR #687) for /models and /agents select menus. The PR's take(25) approach unknowingly regresses this.

Four-Question Framework

1. What problem does it solve?
/models, /agents, /cancel slash commands didn't work reliably against cursor-agent ACP sessions. Three issues: (a) "start a conversation first" dead-end, (b) Discord 3s interaction deadline exceeded on cold-start, (c) cursor-agent uses category=mode while kiro-cli uses category=agent.

2. How does it solve it?

  • defer_ephemeral → edit_response pattern: Defers immediately (buys 15 min), then does slow work, then edits. Solves 3s deadline.
  • pool.get_or_create() pre-flight: Ensures active ACP session before reading configOptions.
  • agent/mode alias table: Treats "agent" and "mode" as equivalent categories.
  • take(25) truncation: Caps select menu at Discord's 25-option limit.
  • docs/cursor.md overhaul: Documents --model auto startup semantics and known limitations.

3. What was considered?
Three iterations documented: v1 had proactive probe (removed), v2 trust-the-protocol, v3 removed vendor-specific pinning. Good iterative refinement.

4. Is it the best approach?
The defer + get_or_create + alias approach is sound. However, the take(25) flat truncation regresses main's pagination support.

Traffic Light

🟢 INFO

  • defer_ephemeral pattern — Correct fix for Discord's 3s interaction deadline. Not on main yet; genuine improvement.
  • get_or_create pre-flight — Eliminates UX dead-end. Well-placed defensive code.
  • agent/mode alias — Clean solution for cross-backend category mismatch.
  • Trust-the-protocol docs — cursor.md additions are excellent.
  • Thorough test plan — Manual E2E against real cursor-agent pod.

🟡 NIT

  1. Use existing SELECT_MENU_PAGE_SIZE constant after rebase (don't introduce duplicate DISCORD_SELECT_MAX).
  2. aliases slice allocation on every call — consider simple || condition instead.

🔴 SUGGESTED CHANGES

  1. Merge conflicts — rebase required. Main's #687 rewrote imports and added pagination infrastructure.
  2. Pagination regression — Main's build_config_select takes a page parameter and supports pagination. The PR replaces this with flat take(25) that silently drops options. After rebase, integrate defer + get_or_create + alias into existing pagination-aware code.
  3. handle_config_select defer/edit_response flow — Verify that comp.defer() + comp.edit_response() produces the intended UX for component interactions.

Reviewed by 超渡法師 🔃 chaodu Backlog triage

Copy link
Copy Markdown
Collaborator

@chaodu-agent chaodu-agent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Needs Rebase + Rework — Valuable improvements, but pagination regression must be fixed

📋 Baseline Check (Step 0)

Checked main branch first. Main now has:

  • SELECT_MENU_PAGE_SIZE = 25 constant
  • build_config_select takes a page parameter with full pagination
  • handle_pagination for ◀/▶ button navigation
  • build_pagination_buttons helper

Main does NOT have: defer_ephemeral pattern, get_or_create pre-flight, or agent/mode alias. These are net-new value from this PR.

Critical finding: The PR's take(25) flat truncation regresses main's existing pagination support.

🧭 Four-Question Framework

1. What problem does this solve?

Multiple UX pain points with cursor-agent Discord slash commands:

  • Discord 3-second interaction deadline causes failures on cold-start
  • Users hit "start a conversation first" dead-end when no conversation exists
  • cursor-agent uses "mode" terminology while kiro-cli uses "agent" — confusing for users
  • docs/cursor.md needed updating for --model auto semantics

2. How does it solve it?

Changes across 3 files (+152/-58):

  • defer_ephemeraledit_response pattern to handle Discord's 3s deadline
  • get_or_create pre-flight check to auto-create conversations
  • Agent/mode category alias so both terminologies work
  • docs/cursor.md overhaul with known limitations section

3. What was considered?

The PR introduces DISCORD_SELECT_MAX = 25 as a new constant, but main already has SELECT_MENU_PAGE_SIZE = 25 serving the same purpose. The take(25) approach was likely written before main gained full pagination support.

4. Is this the best approach?

The defer_ephemeral, get_or_create, and alias features are solid improvements. However, the select menu handling needs rework to integrate with main's pagination system rather than replacing it with flat truncation.

🚦 Traffic Light

🟢 INFOdefer_ephemeraledit_response pattern is the correct solution for Discord's 3-second interaction deadline. Well implemented.

🟢 INFOget_or_create pre-flight eliminates a real UX dead-end. Good improvement.

🟢 INFO — Agent/mode alias is a thoughtful cross-tool consistency fix.

🟢 INFO — docs/cursor.md overhaul with --model auto semantics and known limitations is valuable.

🔴 SUGGESTED CHANGES — PR is in CONFLICTING state. Needs rebase against main.

🔴 SUGGESTED CHANGESPagination regression: Main's build_config_select now takes a page parameter with full ◀/▶ pagination. This PR's take(25) approach drops that support entirely. After rebase, please integrate with the existing pagination system instead of truncating.

🟡 NIT — After rebase, use the existing SELECT_MENU_PAGE_SIZE constant instead of introducing DISCORD_SELECT_MAX. Same value, one source of truth.

🟡 NIT — The aliases slice allocation on every call is unnecessary overhead. A simple category == "agent" || category == "mode" condition would be cleaner and zero-alloc.


Review by 超渡法師 🙏 — the net-new features are 🔥, just need to harmonize with main's pagination after rebase!

@chaodu-agent
Copy link
Copy Markdown
Collaborator

超渡法師 Review — PR #492

1. What problem does it solve?

Discord slash commands /models, /agents, /cancel did not work against cursor-agent acp because: (a) cursor-agent uses category=mode instead of agent, (b) cursor-agent returns 26 models (exceeding Discord's 25-option limit), and (c) the old create_response pattern could not survive a cold-start session spawn within Discord's 3-second interaction deadline.

2. How does it solve it?

  • defer_ephemeralget_or_createedit_response pattern buys time for cold-start session spawns. Clean and correct.
  • agent/mode category alias — handles the kiro-cli vs cursor-agent naming difference. Per ACP spec the reserved value is "mode", so this is correct.
  • take(25) — simple, ACP-agnostic truncation. No vendor literals.
  • Trust-the-protocolsetOk:true is taken at face value; upstream soft-reject bugs are documented and tracked in tracking: cursor-agent ACP compliance issues — upstream bugs affecting /models #493.
  • Docsdocs/cursor.md updated with startup --model auto table, soft-reject explanation, and Known Limitations section.

3. What was considered?

Three iterations documented in commit history:

  • v1: proactive per-model probe + auto-fallback (removed per maintainer feedback)
  • v2: current-value + default[] pinning during truncation (removed — default[] is a cursor-specific literal)
  • v3: plain take(25) — ACP-agnostic, no vendor assumptions

4. Is this the best approach?

The core changes (defer pattern, alias, take(25), trust-the-protocol) are all correct. However, the PR was branched before main gained pagination support, and the diff regresses two features that now exist on main.


Traffic Light

🟢 INFO — Well done

  • defer_ephemeral + get_or_create + edit_response is the correct pattern for Discord interactions that may need >3s. This fixes a real UX dead-end.
  • agent/mode alias table is clean and extensible.
  • take(25) is the right call — no vendor-specific pinning, no complexity.
  • docs/cursor.md Known Limitations section is thorough and links to tracking: cursor-agent ACP compliance issues — upstream bugs affecting /models #493.
  • Commit history shows good iteration discipline (probe removed, pinning removed).

🔴 SUGGESTED CHANGES

  1. Pagination regressionmain now has build_config_components(), build_pagination_buttons(), and handle_pagination() that support ◀/▶ page navigation for >25 options. The PR replaces this with a flat take(25) that silently drops option feat: add agent.preset for simplified install UX #26. The PR needs to be rebased onto current main and integrated with the pagination system instead of replacing it.

  2. Current-value-first sorting regressionmain's build_config_select() sorts the current selection to position 0 so it always appears on page 0. The PR's version iterates options in original order, meaning the current selection could be on any page (or truncated away at position >25). After rebase, the PR should preserve this sorting.

  3. handle_config_command missing defer_ephemeral — The PR correctly adds defer_ephemeral to handle_config_command, handle_cancel_command, and handle_config_select. But main's handle_config_command uses create_response (no defer). The defer pattern is the right approach — but after rebase, ensure handle_pagination also gets the same treatment if needed.

  4. handle_config_select drops UpdateMessage for edit_responsemain uses CreateInteractionResponse::UpdateMessage for the select handler (which is the correct Discord pattern for component interactions — it updates the original ephemeral message in-place). The PR switches to defer + edit_response, which works but creates a visible "thinking..." state. For the select handler specifically, UpdateMessage is preferred because the session is already active (the user just interacted with the menu). Consider keeping UpdateMessage for handle_config_select and only using defer + edit_response for handle_config_command (where cold-start is possible).

🟡 NIT

  • docs/cursor.md:91 — "gpt-5.4/gpt-5.3-codex-spark" model names may become stale quickly. Consider phrasing as "some models on certain accounts" without specific model names, or add a date stamp.

Verdict

🔴 Changes requested — The core approach is sound, but the PR needs a rebase onto current main to integrate with the pagination system (build_config_components, build_pagination_buttons, handle_pagination) instead of regressing it. The agent/mode alias and defer_ephemeral pattern should be preserved during rebase.

@chaodu-agent
Copy link
Copy Markdown
Collaborator

CHANGES REQUESTED 🔴 — Rebase required (168 commits behind, merge conflicts)

The code changes are solid — three meaningful improvements to the Discord slash command flow — but the branch cannot merge in its current state. Please rebase onto main and resolve conflicts before re-requesting review.

四問框架 Analysis

1. What problem does this solve?

Discord slash commands (/models, /agents, /cancel) had two UX issues against cursor-agent:

  1. 3-second deadline: CreateInteractionResponse must be sent within 3s of the interaction. Cold-starting an ACP session (spawning cursor-agent acp) can exceed this, causing a "This interaction failed" error.
  2. "Start a conversation first" dead-end: If no session existed (user never @mentioned the bot), /models returned "No model options available" with no way to recover.
  3. Category mismatch: cursor-agent uses category: "mode" while kiro-cli uses category: "agent"/agents returned empty against cursor-agent.

2. How does it solve them?

Three changes in src/discord.rs:

  • defer_ephemeral + EditInteractionResponse: Buys time by deferring the response, then editing it after the ACP call completes. Applied to /models, /agents, /cancel, and the config select handler.
  • get_or_create pre-flight: Before fetching configOptions, ensures an active ACP session exists. Handles both never-started and pool-evicted sessions.
  • agent/mode aliasing: build_config_select now treats "agent" and "mode" as aliases, so /agents works against both kiro-cli and cursor-agent.

Additionally:

  • Pagination removed: build_pagination_buttons, build_config_components, handle_pagination all deleted. Replaced with a plain take(25) in build_config_select. Since Discord caps StringSelectMenu at 25 options and cursor-agent returns 26 on Pro, only the last one is dropped.

3. What was considered?

The PR description documents three iterations:

  • v1: Proactive per-model probe (silent ping to detect soft-rejects) + auto-fallback to default[]
  • v2: Probe removed per maintainer feedback — trust-the-protocol approach
  • v3: Truncation pinning (current + default[] always included) also removed — plain take(25)

Good evolution. Each simplification is well-justified.

4. Is this the best approach?

The defer + get_or_create + aliasing approach is correct. Two concerns:

  1. Pagination removal is a regression for future backends: cursor-agent returns 26 models (one dropped), but a future backend could return 50+. The pagination code was already working. Removing it saves ~60 lines but loses functionality. Consider keeping it or at minimum documenting the 25-option hard cap as a known limitation.
  2. Aliasing is hardcoded: The agent/mode alias table is a match block. If a third backend uses yet another category name, this needs another code change. A config-driven alias map would be more extensible, but this is a NIT — the current approach is fine for two known backends.
Traffic Light

🔴 SUGGESTED CHANGES

  1. Rebase onto main — 168 commits behind, merge state is CONFLICTING. The diff cannot be reviewed for correctness against current main until conflicts are resolved. This is the only blocker.

  2. Missing Prior Art section — Per RFC: PR Contribution Guidelines, PRs should include research on how OpenClaw and Hermes Agent handle the same problem (Discord interaction deadline management, config option menus). The PR description is otherwise excellent — adding this section would complete it.

🟡 NIT

  1. Pagination removal trade-off — The removed pagination was functional and handled >25 options gracefully. take(25) silently drops options. Consider either (a) keeping pagination, or (b) adding a log warning when options are truncated so operators know items were dropped:

    if opt.options.len() > DISCORD_SELECT_MAX {
        tracing::warn!(category, total = opt.options.len(), shown = DISCORD_SELECT_MAX, "config options truncated to Discord limit");
    }
  2. No tests for aliasing logic — The agent/mode alias matching in build_config_select is new logic with no unit test. A small test with category: "mode" input matched by "agent" lookup would prevent regressions.

  3. handle_config_select double get_or_create — The select interaction can only arrive if the user already has an active session (they just saw the menu). The get_or_create call is defensive but adds latency. Consider documenting why it is needed (pool eviction between interactions) in a code comment — the current comment is good but could note the expected frequency.

🟢 INFO

  1. Excellent PR description — The revision history (v1→v2→v3) is a model for how to document design evolution. The test plan with specific E2E scenarios against a real cursor-agent pod is thorough.

  2. Docs changes are high quality — The startup default table in docs/cursor.md is immediately useful. The soft-reject documentation and upstream ACP compliance gaps section (linking to tracking: cursor-agent ACP compliance issues — upstream bugs affecting /models #493) are exactly the kind of operational knowledge that saves debugging time.

  3. Trust-the-protocol principle — The decision to not work around cursor-agent's setOk:true for unroutable models is architecturally correct. Workarounds in the gateway layer create coupling; surfacing the upstream bug directly is the right call.

  4. config.toml.example comment — Small but valuable: documenting that --model auto is load-bearing prevents a common misconfiguration.


Action needed: Rebase onto main, resolve conflicts, then re-request review. The code quality is good — once the branch is current, this should be a quick re-review.

@chaodu-agent
Copy link
Copy Markdown
Collaborator

🔃 Backlog Sweep — 2026-05-03

Status: 🔴 Stale — rebase required

No new commits since last review (2026-05-03). Branch still has merge conflicts against main. Previous review findings remain valid:

  1. Rebase required — merge conflicts prevent merge
  2. Pagination regressionmain now has pagination support that this PR removes
  3. agent/mode aliasing — good approach, needs integration with current main

The core changes (defer_ephemeral, get_or_create pre-flight, category aliasing) are solid. Waiting on contributor to rebase and address review feedback.

Label: closing-soon recommended if no activity within 14 days.

@chaodu-agent
Copy link
Copy Markdown
Collaborator

Closing due to staleness — merge conflicts, no contributor activity. The feature can be re-proposed in a fresh PR against current main.

auto-merge was automatically disabled May 3, 2026 13:00

Pull request was closed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(cursor): wire up Cursor agent to /models, /agents, /cancel slash commands

5 participants